package com.tarot.aspect.logger.aspect;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.tarot.aspect.logger.dto.common.RequestErrorInfo;
import com.tarot.aspect.logger.dto.common.RequestInfo;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

/**
 * 请求日志切面优化：
 * 解决高并发请求下确实会出现请求之间打印日志串行的问题？
 * 方案：只要将多行打印信息合并为一行就可以了，因此构造一个对象
 *
 * @author zhaohejia
 * @version 1.0.0
 * @date 2020/11/6
 */
@Component
@Aspect
public class RequestLogAspect {

    /**
     * 日志
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(RequestLogAspect.class);

    /**
     * 切点：Controller包下的所有类下的方法
     */
    @Pointcut("execution(* com.tarot.aspect.logger.controller..*(..))")
    public void requestServer() {
    }

    /**
     * 环绕通知：在进入Controller方法后开始执行逻辑代码前，打印了入参、结果以及耗时
     *
     * @param proceedingJoinPoint 切点
     */
    @Around("requestServer()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        assert attributes != null;
        HttpServletRequest request = attributes.getRequest();
        long start = System.currentTimeMillis();

        //执行逻辑
        //获取响应结果
        Object result = proceedingJoinPoint.proceed();

        //耗时
        long timeCost = System.currentTimeMillis() - start;
        //请求ip
        String ip = request.getRemoteAddr();
        //请求地址
        StringBuffer url = request.getRequestURL();
        //请求方法
        String httpMethod = request.getMethod();
        //类方法名 eg:类.方法
        //请求类名
        String className = proceedingJoinPoint.getSignature().getDeclaringTypeName();
        //请求方法名
        String methodName = proceedingJoinPoint.getSignature().getName();
        String classMethod = String.format("%s.%s", className, methodName);
        //请求参数
        Map<String, Object> requestParams = getRequestParams(proceedingJoinPoint);

        //组装请求日志
        RequestInfo requestInfo = new RequestInfo();
        requestInfo.setIp(ip)
                .setClassMethod(classMethod)
                .setHttpMethod(httpMethod)
                .setRequestParams(requestParams)
                .setUrl(url.toString())
                .setResponseResult(result)
                .setTimeCost(timeCost);

        formatPrint("执行日志打印", requestInfo);
        return result;
    }

    /**
     * 异常环绕通知
     *
     * @param joinPoint 断点
     * @param e 异常
     */
    @AfterThrowing(pointcut = "requestServer()", throwing = "e")
    public void doAfterThrow(JoinPoint joinPoint, RuntimeException e) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        assert attributes != null;
        HttpServletRequest request = attributes.getRequest();

        //组装请求异常日志
        RequestErrorInfo requestErrorInfo = new RequestErrorInfo();
        requestErrorInfo.setIp(request.getRemoteAddr())
                .setHttpMethod(request.getMethod())
                .setClassMethod(String.format("%s.%s", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName()))
                .setRequestParams(getRequestParams(joinPoint))
                .setException(e.toString());

        formatPrint("异常日志打印", requestErrorInfo);
    }

    /**
     * 获取请求参数集合
     *
     * @param joinPoint 切点
     * @return 请求参数集合
     */
    private Map<String, Object> getRequestParams(JoinPoint joinPoint) {

        //参数名
        String[] paramNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
        //参数值
        Object[] paramValues = joinPoint.getArgs();

        Map<String, Object> requestParams = new HashMap<>(paramNames.length);
        //以key=参数名,value=参数值组装入参对象
        for (int i = 0; i < paramNames.length; i++) {
            Object paramValue = paramValues[i];
            //如果是文件对象
            if (paramValue instanceof MultipartFile) {
                MultipartFile file = (MultipartFile) paramValue;
                //文件展示的值为文件名
                paramValue = file.getOriginalFilename();
            }
            requestParams.put(paramNames[i], paramValue);
        }

        return requestParams;
    }

    /**
     * 格式化打印日志
     *
     * @param info 打印信息
     * @param loggerType 类型名
     */
    private void formatPrint(String loggerType, Object info) {
        //格式化打印
        String string = JSON.toJSONString(info);
        JSONObject parseObject = JSONObject.parseObject(string);
        String pretty = JSON.toJSONString(parseObject, SerializerFeature.PrettyFormat, SerializerFeature.WriteMapNullValue, SerializerFeature.WriteDateUseDateFormat);
        LOGGER.info("\n----------------------------------------{}---------------------------------------- \n{}\n", loggerType, pretty);
    }
}
